#!/usr/bin/env python
#
'''
Created on 2009-3-14

@author: Wang_Yuan
'''

import datetime
import re
import random
import xml.dom.minidom as minidom
import os
import os.path

from utils import *

class WordData:
    '''
    This is a class to represent a word. 
    '''
    
    def __init__(self):
        '''
        Constructor
        '''
        self.word = ''
        self.pronunciation = ''
        self.meanings = Meanings()
        self.examples = set()
        self.records = []
        
    def isPhrase(self):
        return ' ' in self.word
    
    def isEmpty(self):
        return not (self.word or self.pronunciation or self.meanings or self.examples or self.records)
    
    def absorb(self, wd):
        if self.isSameWord(wd):
            wd.pronunciation = wd.pronunciation.strip()
            if not self.pronunciation.strip():
                self.pronunciation = wd.pronunciation
            elif self.pronunciation.strip() != wd.pronunciation.strip() and wd.pronunciation and wd.pronunciation not in self.pronunciation:
                self.pronunciation += (u', ' + wd.pronunciation)
            self.addMeaning(wd.getMeaningStr())
            self.examples = self.examples.union(wd.examples)
            self.records += wd.records
            self.records.sort(key=ExaminRecord.getDate)
    
    def addRecord(self, record):
        self.records.insert(0, record)
        
    def clearRecords(self):
        del self.records[:]
        
    def getRecordScores(self):
        if self.records:
            return map(lambda record: record.score, self.records)
        else:
            return []
    
    def getMeaningStr(self):
        return unicode(self.meanings)
    
    def getNonEnglishAtomMeaningList(self):
        return self.meanings.getNonEnglishAtomMeaningList()
    
    def getEnglishAtomMeaningList(self):
        return self.meanings.getEnglishAtomMeaningList()
    
    def getNonEnglishMeaningList(self):
        return self.meanings.getNonEnglishMeaningList()
    
    def getEnglishMeaningList(self):
        return self.meanings.getEnglishMeaningList()
    
    def getNonEnglishMeaningStr(self):
        return self.meanings.getNonEnglishMeaningStr()
    
    def getEnglishMeaningStr(self):
        return self.meanings.getEnglishMeaningStr()
    
    def setMeaningStr(self, meanings):
        self.meanings.set(meanings)
    
    def addMeaning(self, meaning):
        self.meanings.add(meaning)
        
    def __getShuffledList(self, l):
        random.shuffle(l)
        return l
    
    def getShuffledMeanings(self):
        return self.__getShuffledList(self.meanings.getMeaningList())
    
    def getShuffledEnglishMeanings(self):
        return self.__getShuffledList(self.meanings.getEnglishMeaningList())
    
    def getShuffledNonEnglishMeanings(self):
        return self.__getShuffledList(self.meanings.getNonEnglishMeaningList())
    
    def hasNonEnglishMeanings(self):
        return self.meanings.hasNonEnglishMeanings()
        
    def hasEnglishMeanings(self):
        return self.meanings.hasEnglishMeanings()
    
    def hasExamples(self):
        return bool(self.examples)
    
    def getRandomExample(self):
        if self.examples: 
            return random.choice(list(self.examples))
        else:
            return None
    
    def getShuffledExamples(self):
        l = list(self.examples)
        random.shuffle(l)
        return l
    
    def getExampleList(self):
        return list(self.examples)
    
    def __str__(self):
        return '%s\n\n%s\n\n%s\n\n%s' % (self.word, self.getMeaningStr(), '\n'.join(map(lambda x: str(x), self.examples)), '\n'.join(map(lambda x: str(x), self.records)))
    
    def __unicode__(self):
        return unicode(os.linesep).join([self.word, self.pronunciation, '', self.getMeaningStr(), ''] + map(lambda x: unicode(x), self.examples) + map(lambda x: unicode(x), self.records))
    
    def __repr__(self):
        return str(self)
    
    def __eq__(self, other):
        return type(self) == type(other) and self.word == other.word and self.pronunciation == other.pronunciation and self.meanings == other.meanings and self.examples == other.examples and self.records == other.records
    
    def __ne__(self, other):
        return not self.__eq__(other)
    
    def __hash__(self):
        return hash(unicode(self)) * 31 + hash(type(self))
    
    def __nonzero__(self):
        return not self.isEmpty()
    
    def isSameWord(self, other):
        return type(self) == type(other) and self.word == other.word


class Meanings:
    '''
    This is a class to represent the meanings of a word. 
    '''
    
    ALT_WORD_CLASSES = {
                        'adj': 'a', 
                        'adv': 'ad',
                        }
    
    def __init__(self):
        self.enMeanings = {}
        self.meanings = {}
        
    def __eq__(self, other):
        return type(self) == type(other) and self.meanings == other.meanings and self.enMeanings == other.enMeanings
    
    def __ne__(self, other):
        return not self.__eq__(other)
        
    def __len__(self):
        return len(self.meanings) + len(self.enMeanings)
    
    def __convMultiMeanings(self, meanings):
        d = {}
        ed = {}
        l = meanings.split('\n')
        result = set()
        for line in l:
            line = line.strip()
            if line:
                wordClass, ms = line.split('.', 1)
                altWordClass = self.ALT_WORD_CLASSES.get(wordClass)
                if altWordClass:
                    wordClass = altWordClass
                existsMs = d.get(wordClass)
                enExistsMs = ed.get(wordClass)
                if existsMs is None:
                    existsMs = set()
                if enExistsMs is None:
                    enExistsMs = set()
                for m in ms.split(','):
                    m = m.strip()
                    if m and self.__isEnglishMeaning(m):
                        enExistsMs.add(m)
                    elif m:
                        existsMs.add(m)
                if existsMs:
                    d[wordClass] = existsMs
                if enExistsMs:
                    ed[wordClass] = enExistsMs
                    
        return d, ed
    
    def __isEnglishMeaning(self, meaning):
        for c in meaning:
            try:
                if not (0x20 <= ord(c) <= 0x7f):
                    return False
            except:
                return False
        return True
        
    
    def add(self, meaning):
        d, ed = self.__convMultiMeanings(meaning)
        for k, v in d.items():
            exists = self.meanings.get(k)
            if not exists:
                exists = set()
            self.meanings[k] = exists.union(v)
        for k, v in ed.items():
            exists = self.enMeanings.get(k)
            if not exists:
                exists = set()
            self.enMeanings[k] = exists.union(v)
        
    def set(self, meanings):
        d, ed = self.__convMultiMeanings(meanings)
        self.meanings = d
        self.enMeanings = ed
        
    def __getMeaningList(self, meaningDict):
        return map(lambda m: m[0] + u'.  ' + u', '.join(list(m[1])), meaningDict.items())
    
    def __getAtomMeaningList(self, meaningDict):
        l = []
        for k, v in meaningDict.items():
            for m in v:
                l.append(k + u'.  ' + m)
        return l
    
    def __getMeaningStr(self, meaningDict):
        return u'\n'.join(self.__getMeaningList(meaningDict)).strip()
    
    def getNonEnglishAtomMeaningList(self):
        return self.__getAtomMeaningList(self.meanings)
    
    def getEnglishAtomMeaningList(self):
        return self.__getAtomMeaningList(self.enMeanings)
    
    def getNonEnglishMeaningList(self):
        return self.__getMeaningList(self.meanings)
    
    def getEnglishMeaningList(self):
        return self.__getMeaningList(self.enMeanings)
    
    def getMeaningList(self):
        return self.getNonEnglishMeaningList() + self.getEnglishMeaningList()
    
    def getNonEnglishMeaningStr(self):
        return self.__getMeaningStr(self.meanings)
    
    def getEnglishMeaningStr(self):
        return self.__getMeaningStr(self.enMeanings)
    
    def hasNonEnglishMeanings(self):
        return bool(self.meanings)
    
    def hasEnglishMeanings(self):
        return bool(self.enMeanings)
        
    def __str__(self):
        return '\n'.join([self.getNonEnglishMeaningStr(), self.getEnglishMeaningStr()])
    
    def __unicode__(self):
        return u'\n'.join([self.getNonEnglishMeaningStr(), self.getEnglishMeaningStr()])
    
    def __repr__(self):
        return str(self)

class ExaminRecord:
    '''
    This is a class to represent a examination record for a word. 
    '''
    
    PATTERN = "(\d+)@(.+)"
    FORMAT = '%Y-%m-%d %H:%M:%S'
    
    WRONG = 0
    CORRECT = 5
    HINT = 3
    
    def __init__(self, record = None):
        if record == None:
            self.score = 0
            self.date = datetime.datetime.now()
        else:
            m = re.match(self.PATTERN, record)
            self.score = int(m.group(1))
            self.date = datetime.datetime.strptime(m.group(2), self.FORMAT)
        
    def getFormatedDate(self):
        return self.date.strftime(ExaminRecord.FORMAT)
    
    def getDate(self):
        return self.date
    
    def __str__(self):
        return '%s@%s' % (self.score, self.getFormatedDate())
    
    def __unicode__(self):
        return u'%s@%s' % (self.score, self.getFormatedDate())
    
    def __repr__(self):
        return str(self)
    
    def __eq__(self, other):
        return type(self) == type(other) and unicode(self) == unicode(other)
    
    def __ne__(self, other):
        return not self.__eq__(other)
    
    def __hash__(self):
        return hash(unicode(self)) * 31 + hash(type(self))


class WordExample:
    '''
    This is a class to represent the word example. 
    '''
    
    PATTERN = "([^_\|]*)_([^_]+)_([^\|]*)(\|(.*))?"

    def __init__(self, example=''):
        self.content = ''
        self.front = ''
        self.after = ''
        self.answer = ''
        self.translation = ''
        self.importExample(example)

    def importExample(self, example):
        self.content = ''
        if example:
            pattern = WordExample.PATTERN
            m = re.match(pattern, example)
            self.answer = m.group(2).strip()
            self.front = m.group(1).strip()
            self.after = m.group(3).strip()
            self.translation = m.group(5)
            self.content = example
            
    def __str__(self):
        return self.content
    
    def __unicode__(self):
        return unicode(self.content)
    
    def __repr__(self):
        return str(self)
    
    def __eq__(self, other):
        return type(self) == type(other) and self.content == other.content
    
    def __ne__(self, other):
        return not self.__eq__(other)
    
    def __hash__(self):
        return hash(self.content) * 31 + hash(type(self))
    
    def __len__(self):
        return len(self.content)
    
    def normalStr(self):
        return re.sub('_', '', self.content, 2)
    
    def originalPart(self):
        return self.normalStr().split('|', 1)[0]
    
class Vocabulary:
    '''
    This is a class to represent the vocabulary. 
    '''
    
    EXT = 'ivo'
    NO_FILE = '[Unsaved]'
    
    def __init__(self):
        self.clear()
        self.processor = XmlProcessor(self)
        
    def clear(self):
        self.data = []
        self.words = []
        self.fileName = self.NO_FILE
    
    def search(self, word):
        index = self.index(word)
        if index:
            return self.data[self.index(word)]
        else:
            return None
        
    def index(self, word):
        if type(word) != str:
            word = word.word
        if word:
            if len(self.words) != len(self.data):
                self.words = map(lambda wd: wd.word, self.data)
            index = self.words.index(word)
            return index
        else:
            return None
        
    def getExamWordList(self, wordList=None, amount=None):
        if wordList:
            l = wordList
        else:
            l = filter(mustExam, self.data)
        if not amount or len(l) == amount:
            return l
        elif len(l) > amount:
            return l[:amount]
        else:
            # len(l) < amount
            data = list(self.data)
            random.shuffle(data)
            for c in data:
                if len(l) < amount:
                    if c not in l:
                        l.append(c)
                else:
                    break
            return l
        
        
    def add(self, word):
        existWord = self.search(word)
        if not existWord:
            self.data.append(word)
            self.words.append(word.word)
        else:
            existWord.meanings = existWord.meanings.union(word.meanings)
            existWord.examples = existWord.examples.union(word.examples)
            
    def remove(self, word):
        index = self.index(word)
        if index:
            del self.data[index]
            del self.words[index]
            
    def clearAllRecords(self):
        for d in self.data:
            d.clearRecords()
            
    def toXml(self):
        return self.processor.vocabulary2xml()
    
    def fromXml(self, xml):
        self.processor.xml2vocabulary(xml)
        
    def save(self):
        if os.path.exists(self.fileName):
            f = file(self.fileName, 'w')
            f.write(self.toXml())
            f.close()
            return True
        else:
            return False
        
    def load(self):
        if os.path.exists(self.fileName):
            f = file(self.fileName, 'r')
            xml = f.read()
            f.close()
            self.fromXml(xml)

class XmlProcessor:
    '''
    A class to do xml process. 
    '''
    
    ENCODING = 'UTF-8'
    VOCABULARY = 'vocabulary'
    WORD_DATA = 'wordData'
    WORD = 'word'
    PRONUNCIATION = 'pronunciation'
    MEANING = 'meaning'
    EXAMPLE = 'example'
    RECORD = 'record'
    
    def __init__(self, vocabulary):
        self.vocabulary = vocabulary
    
    def xml2vocabulary(self, xml):
        doc = minidom.parseString(xml)
        self.vocabulary.data = []
        self.vocabulary.words = []
        loadedWords = set()
        for element in doc.childNodes:
            # <vocabulary>
            if element.tagName == self.VOCABULARY:
                for e in element.childNodes:
                    # <wordData>
                    if e.nodeType == e.ELEMENT_NODE and e.tagName == self.WORD_DATA:
                        wordData = self.processWordDataXml(e)
                        if wordData.word not in loadedWords:
                            self.vocabulary.data.append(wordData)
                            self.vocabulary.words.append(wordData.word)
                            loadedWords.add(wordData.word)
                        else:
                            index = self.vocabulary.words.index(wordData.word)
                            oldWordData = self.vocabulary.data[index]
                            oldWordData.absorb(wordData)
    
    def processWordDataXml(self, node):
        wd = WordData()
        wd.word = node.getAttribute(self.WORD)
        wd.pronunciation = node.getAttribute(self.PRONUNCIATION)
        for e in node.childNodes:
            if e.nodeType == e.ELEMENT_NODE:
                # <meaning>
                if e.tagName == self.MEANING:
                    for v in e.childNodes:
                        if v.nodeType == v.TEXT_NODE:
                            wd.addMeaning(v.nodeValue)
                            # wd.meanings.add(v.nodeValue)
                # <example>
                elif e.tagName == self.EXAMPLE:
                    for v in e.childNodes:
                        if v.nodeType == v.TEXT_NODE:
                            value = v.nodeValue.strip()
                            if value:
                                wd.examples.add(WordExample(value))
                # <example>
                elif e.tagName == self.RECORD:
                    for v in e.childNodes:
                        if v.nodeType == v.TEXT_NODE:
                            wd.records.append(ExaminRecord(v.nodeValue))
        return wd
                
        
            
    def vocabulary2xml(self):
        doc = minidom.Document()
        vocabulary = doc.createElement(self.VOCABULARY)
        doc.appendChild(vocabulary)
        for word in self.vocabulary.data:
            wordDataNode = doc.createElement(self.WORD_DATA)
            vocabulary.appendChild(wordDataNode)
            wordDataNode.setAttribute(self.WORD, word.word)
            wordDataNode.setAttribute(self.PRONUNCIATION, word.pronunciation)
            
            meaningNode = doc.createElement(self.MEANING)
            wordDataNode.appendChild(meaningNode)
            meaningText = doc.createTextNode(unicode(word.meanings))
            meaningNode.appendChild(meaningText)
#            for meaning in word.meanings:
#                meaningNode = doc.createElement(self.MEANING)
#                wordDataNode.appendChild(meaningNode)
#                meaningText = doc.createTextNode(meaning)
#                meaningNode.appendChild(meaningText)
            for example in word.examples:
                exampleNode = doc.createElement(self.EXAMPLE)
                wordDataNode.appendChild(exampleNode)
                exampleText = doc.createTextNode(unicode(example))
                exampleNode.appendChild(exampleText)
            for record in word.records:
                recordNode = doc.createElement(self.RECORD)
                wordDataNode.appendChild(recordNode)
                recordText = doc.createTextNode(unicode(record))
                recordNode.appendChild(recordText)
                
        return doc.toxml(self.ENCODING)
